// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Data;
using SnapObjects.Commons;

namespace SnapObjects.Data
{
    public class AdoDbParameter : IAdoDbParameter
    {
        private object _value;
        private Type _clrType;
        private readonly ParamValue _param;

        public AdoDbParameter(ParamValue param)
            : this(
                  param.Name,
                  param.Value,
                  param.DataType,
                  param.Direction,
                  param.TypeName,
                  param.Precision,
                  param.Scale,
                  -1,
                  param.Size,
                  param.ArrayBindSize)
        {
            _param = param;
        }

        public AdoDbParameter(
            string name,
            object value,
            Type clrType,
            ParameterDirection direction = ParameterDirection.Input)
            : this(name, value, clrType, direction, String.Empty, 0, 0, -1)
        {
        }

        internal AdoDbParameter(
           string name,
           object value,
           Type clrType,
           ParameterDirection direction,
           string typeName,
           byte precision,
           byte scale,
           int position)
            : this(name, value, clrType, direction, typeName, precision, scale, position, 0, null)
        {
        }

        internal AdoDbParameter(
           string name,
           object value,
           Type clrType,
           ParameterDirection direction,
           string typeName,
           byte precision,
           byte scale,
           int position,
           int size,
           int[] arrayBindSize)
            :this(name, value, false, clrType, direction, typeName, precision, scale, position, size, arrayBindSize)
        {
        }

        internal AdoDbParameter(
           string name,
           object value,
           bool hasDefaultValue,
           Type clrType,
           ParameterDirection direction,
           string typeName,
           byte precision,
           byte scale,
           int position,
           int size,
           int[] arrayBindSize)
        {
            this.ParameterName = name;
            this.Value = value;
            this.Direction = direction;

            if (clrType != null)
            {
                this.ClrType = clrType;
            }

            if (!String.IsNullOrWhiteSpace(typeName))
            {
                this.TypeName = typeName;
            }

            this.Precision = precision;
            this.Scale = scale;
            this.Position = position;
            this.Size = size;
            this.ArrayBindSize = arrayBindSize;

            this.HasDefaultValue = hasDefaultValue;

            this.InitNumberPrecision();
        }

        public string ParameterName { get; set; }

        public object Value
        {
            get
            {
                if (this.ValueConverter != null)
                {
                    _value = this.Direction == ParameterDirection.Output ?
                        this.ValueConverter.ConvertToProperty(_value) :
                        this.ValueConverter.ConvertFromProperty(_value);
                }
                else
                {
                    if (_value != null &&
                        this.Direction == ParameterDirection.InputOutput &&
                        this.Precision > 0 &&
                        (this.ClrType == typeof(decimal) || this.ClrType == typeof(decimal?)))
                    {

                        _value = Math.Round((decimal)_value, this.Scale, MidpointRounding.AwayFromZero);
                    }
                }

                return _value;
            }
            set
            {
                _value = value;

                if (_value != null)
                {
                    this.ClrType = _value.GetType();
                }

                if (_param != null)
                {
                    _param.Value = _value;
                }
            }
        }

        public Type ClrType
        {
            get
            {
                if (this.ValueConverter != null)
                {
                    return this.ValueConverter.DbFieldClrType;
                }

                return _clrType;
            }
            set
            {
                _clrType = value;

                if (_clrType != null)
                {
                    this.Nullable = _clrType.IsNullable();
                }
            }
        }

        public bool Nullable { get; set; }
        public bool HasDefaultValue { get; }
        public ParameterDirection Direction { get; internal set; }

        public string TypeName { get; internal set; }

        internal byte Precision { get; set; }

        internal byte Scale { get; set; }

        internal int Position { get; set; }

        internal int Size { get; }

        internal int[] ArrayBindSize { get; }

        internal ValueConverter ValueConverter { get; set; }

        private void InitNumberPrecision()
        {
            if (this.Direction == ParameterDirection.Input &&
                this.Value != null &&
                this.ClrType != null &&
                this.ClrType == typeof(decimal) &&
                this.Scale <= 0 &&
                this.Precision <= 0)
            {
                var str = ValueConvert.ToString(this.Value);

                var nums = str.Split('.');

                if (nums.Length == 2)
                {
                    this.Precision = nums[0].Equals("0") ? (byte)nums[1].Length
                        : (byte)(nums[0].Length + nums[1].Length);

                    this.Scale = (byte)nums[1].Length;
                }
                else
                {
                    this.Precision = (byte)nums[0].Length;
                }
            }
        }
    }
}
